use std::{fmt::Debug, mem};

use reqwest::Client;
use ruma::{
    api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken},
    ServerName,
};
use tracing::{debug, info};

use crate::{Error, Result};

use super::Service;

impl Service {
    pub async fn sender(&self) -> Result<()> {
        println!("Sending email");
        Ok(())
    }

    #[tracing::instrument(skip(self, client, req), name = "send")]
    pub async fn send<T>(
        &self,
        client: &Client,
        dest: &ServerName,
        req: T,
    ) -> Result<T::IncomingResponse>
    where
        T: OutgoingRequest + Debug + Send,
    {
        let request = self.prepare::<T>(dest, req).await?;
        self.execute::<T>(dest, request, client).await
    }

    async fn execute<T>(
        &self,
        _dest: &ServerName,
        request: reqwest::Request,
        client: &Client,
    ) -> Result<T::IncomingResponse>
    where
        T: OutgoingRequest + Debug + Send,
    {
        let url = request.url().clone();
        let method = request.method().clone();

        debug!(?method, ?url, "Sending request");
        match client.execute(request).await {
            Ok(mut response) => {
                let status = response.status();
                let mut response_builder = http::response::Builder::new()
                    .status(status)
                    .version(response.version());
                mem::swap(
                    response_builder
                        .headers_mut()
                        .expect("headers should always available"),
                    response.headers_mut(),
                );

                let ret_response = response_builder
                    .body(
                        response
                            .bytes()
                            .await
                            .expect("response body should be available"),
                    )
                    .expect("response built should be valid");
                if !status.is_success() {
                    return Err(Error::BadServerResponse("Invalid status code"));
                } else {
                    Ok(IncomingResponse::try_from_http_response(ret_response).unwrap())
                }
            }
            Err(error) => Err(error.into()),
        }
    }

    async fn prepare<T>(&self, dest: &ServerName, req: T) -> Result<reqwest::Request>
    where
        T: OutgoingRequest + Debug + Send,
    {
        const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11];
        const SATIR: SendAccessToken<'_> = SendAccessToken::IfRequired("");

        info!("Preparing request");
        let http_request = req
            .try_into_http_request::<Vec<u8>>(&dest.as_str(), SATIR, &VERSIONS)
            .map_err(|_e| Error::BadServerResponse("Invalid destination"))?;

        // TODO: Implement sign_request
        // sign_request::<T>(&self.services.global, dest, &mut http_request);

        let request = reqwest::Request::try_from(http_request)?;

        Ok(request)
    }
}
